package util

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/golang/glog"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	endedgecloud "eccgateway/pkg/api/endedgecloud/v1"
	"eccgateway/pkg/config"
)

var (
	kubernetesAddress        string
	kubernetesClient         *http.Client
	kubernetesClientSyncOnce sync.Once
)

var (
	NotFoundError = errors.New("not found")
)

const (
	clusterNameKey = "endedgecloud.zjlab.io/cluster"
)

func GetKubernetesClient() *http.Client {
	initKubernetesClientFunc := func() {
		if len(kubernetesAddress) == 0 {
			kubernetesAddress = strings.TrimSuffix(config.GetConfig().String("kubernetesAddress"), "/")
			glog.Infof("kubernetesAddress is %v", kubernetesAddress)
		}

		var ssl *tls.Config = nil
		kubernetesCertFile := config.GetConfig().String("kubernetesCert")
		kubernetesKeyFile := config.GetConfig().String("kubernetesKey")
		if kubernetesCertFile != "" || kubernetesKeyFile != "" {
			glog.Infof("kubernetesCertFile: %v, kubernetesKeyFile: %v", kubernetesCertFile, kubernetesKeyFile)
			cert, err := tls.LoadX509KeyPair(kubernetesCertFile, kubernetesKeyFile)
			if err != nil {
				panic(fmt.Sprintf("initialize kubernetes certificate failed, err: %v", err))
			}
			ssl = &tls.Config{
				Certificates:       []tls.Certificate{cert},
				InsecureSkipVerify: true,
			}
		}

		kubernetesClient = &http.Client{
			Transport: &http.Transport{
				DialContext: (&net.Dialer{
					Timeout:   30 * time.Second,
					KeepAlive: 30 * time.Second,
					DualStack: true,
				}).DialContext,
				TLSClientConfig:       ssl,
				MaxIdleConns:          100,
				IdleConnTimeout:       90 * time.Second,
				TLSHandshakeTimeout:   10 * time.Second,
				ExpectContinueTimeout: 1 * time.Second,
			},
		}
	}
	kubernetesClientSyncOnce.Do(initKubernetesClientFunc)
	return kubernetesClient
}

func GetCluster(name string) (*endedgecloud.Cluster, error) {
	client := GetKubernetesClient()

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/apis/endedgecloud.zjlab.io/v1/clusters/%s", kubernetesAddress, name), nil)
	if err != nil {
		glog.Errorf("construct request to get cluster failed, err: %v", err)
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		glog.Errorf("send request to get cluster failed, err: %v", err)
		return nil, err
	}

	if resp.StatusCode == http.StatusNotFound {
		return nil, NotFoundError
	}

	requestBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		status := metav1.Status{}
		err = json.Unmarshal(requestBody, &status)
		if err != nil {
			glog.Errorf("unmarshal response body failed, cluster name: %v, err: %v", name, err)
			return nil, fmt.Errorf("get cluster failed")
		}
		return nil, fmt.Errorf("%v", status.Message)
	}

	cluster := endedgecloud.Cluster{}
	err = json.Unmarshal(requestBody, &cluster)
	if err != nil {
		glog.Errorf("unmarshal response body failed, cluster name: %v, err: %v", name, err)
		return nil, fmt.Errorf("get cluster failed")
	}

	return &cluster, nil
}

func GetNamespaceListByCluster(clusterName string) (*corev1.NamespaceList, error) {
	client := GetKubernetesClient()

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/namespaces?labelSelector=%s%%3D%s", kubernetesAddress, clusterNameKey, clusterName), nil)
	if err != nil {
		glog.Errorf("construct request to get namespace list failed, err: %v", err)
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		glog.Errorf("send request to get namespace list failed, err: %v", err)
		return nil, err
	}

	if resp.StatusCode == http.StatusNotFound {
		return nil, NotFoundError
	}

	requestBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		status := metav1.Status{}
		err = json.Unmarshal(requestBody, &status)
		if err != nil {
			glog.Errorf("unmarshal response body failed, cluster name: %v, err: %v", clusterName, err)
			return nil, fmt.Errorf("get namespace list failed")
		}
		return nil, fmt.Errorf("%v", status.Message)
	}

	namespaceList := corev1.NamespaceList{}
	err = json.Unmarshal(requestBody, &namespaceList)
	if err != nil {
		glog.Errorf("unmarshal response body failed, cluster name: %v, err: %v", clusterName, err)
		return nil, fmt.Errorf("get namespace list failed")
	}

	return &namespaceList, nil
}